Skip to content

Fix Android layout jump when navigating with IME open and NavBarIsVisible=false#34621

Open
jpd21122012 wants to merge 3 commits intodotnet:mainfrom
jpd21122012:fix/34584-Shell-NavBar-SafeAreaEdges
Open

Fix Android layout jump when navigating with IME open and NavBarIsVisible=false#34621
jpd21122012 wants to merge 3 commits intodotnet:mainfrom
jpd21122012:fix/34584-Shell-NavBar-SafeAreaEdges

Conversation

@jpd21122012
Copy link
Copy Markdown
Contributor

Description

On Android, navigating from a page with the soft keyboard (IME) open to a page with Shell.NavBarIsVisible="False" causes the destination page to initially render under the status bar and then jump into the correct position.

This behavior is more noticeable when the destination page performs heavier UI work, and in some cases the layout may remain incorrectly positioned.

Root Cause

During ShellRenderer.SwitchFragment, the fragment transaction is committed while the IME is still visible. Android continues to report IME-related WindowInsets, causing the new layout to be measured with incorrect top insets.

Once the IME state stabilizes, the layout is corrected, resulting in a visible jump.

Fix

Dismiss the soft keyboard before performing the fragment transaction:

  • Detect IME visibility using IsSoftInputShowing
  • Call HideSoftInput() prior to FragmentTransaction

This ensures that WindowInsets are stable before the new layout is measured.

Result

  • Eliminates layout jump when navigating with keyboard open
  • Ensures correct layout positioning from initial render
  • Improves Shell navigation consistency on Android

Testing

Added a UI test (Issue34584) that:

  • Opens the keyboard by focusing an Entry
  • Navigates to a page with Shell.NavBarIsVisible="False"
  • Verifies that content is laid out below the status bar (Y > 0)

Note: The test validates final layout correctness, not visual animation.

Related Issues

Copilot AI review requested due to automatic review settings March 24, 2026 19:56
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 24, 2026

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 34621

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 34621"

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes an Android Shell navigation layout jump when transitioning while the soft keyboard (IME) is visible—especially when navigating to destinations with Shell.NavBarIsVisible=false.

Changes:

  • Dismisses the soft keyboard before committing the fragment transaction in ShellRenderer.SwitchFragment.
  • Adds a HostApp repro page for issue #34584 (keyboard open → navigate to NavBar hidden page).
  • Adds an Android UI test intended to validate the destination content is not laid out under the status bar.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellRenderer.cs Attempts to hide IME before fragment replacement to stabilize insets/layout.
src/Controls/tests/TestCases.HostApp/Issues/Issue34584.cs Adds a Shell-based repro page with an Entry + navigation to a NavBar-hidden destination.
src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue34584.cs Adds an Android UI test to verify final element Y-position is below the status bar after navigation.

Update the test to trigger navigation from Entry.Completed instead of a button tap,
ensuring the soft keyboard (IME) remains visible at the moment navigation occurs.

This makes the test deterministic and accurately reproduces the original issue scenario.
@github-actions
Copy link
Copy Markdown
Contributor

🧪 PR Test Evaluation

Overall Verdict: ⚠️ Tests need improvement

The test exercises the correct code path and the fix is Android-only with a matching platform guard, but the assertion is weak and there are minor issues (unused HostApp element, redundant null check, missing edge cases).

👍 / 👎 — Was this evaluation helpful? React to let us know!

📊 Expand Full Evaluation

PR Test Evaluation Report

PR: #34621 — Fix Android layout jump when navigating with IME open and NavBarIsVisible=false
Test files evaluated: 1 (UI Test)
Fix files: 1 (ShellRenderer.cs)


Overall Verdict

⚠️ Tests need improvement

The test correctly exercises the fix's code path, but the assertion (Y > 0) is too weak to reliably catch regressions, and a NavigateButton element is defined in the HostApp but never used in the test.


1. Fix Coverage — ✅

The fix calls rootView.HideSoftInput() in SwitchFragment when the IME is showing. The test opens the keyboard (via App.Tap("Entry") + App.EnterText), triggers navigation via App.PressEnter() (which fires entry.Completed → GoToAsync), then checks the label's position on the destination page. This correctly exercises the patched code path and would fail if the fix were reverted (content would render at Y=0 under the status bar).


2. Edge Cases & Gaps — ⚠️

Covered:

  • Navigation with keyboard open via IME action (Enter key)
  • Destination page with NavBarIsVisible=False

Missing:

  • Navigation via button tapNavigateButton is defined in the HostApp but never used in the test. The fix in SwitchFragment should also apply when navigating by tapping a button with the keyboard still open; this path is untested.
  • Navigation without keyboard open — No regression check confirming normal navigation still works correctly after the fix.
  • Threshold for "under status bar" — The assertion Y > 0 would pass even at Y = 1px, which could still be visually wrong. A more meaningful assertion would check Y >= statusBarHeight (or use a screenshot comparison).

3. Test Type Appropriateness — ✅

Current: UI Test (Appium)
Recommendation: Appropriate. This fix requires native IME interaction (soft keyboard) and a full Shell navigation lifecycle with fragment switching. Device tests could theoretically cover part of this, but orchestrating the IME-active state during navigation is significantly easier with Appium. UI test is the right choice here.


4. Convention Compliance — ⚠️

  • ✅ Class named Issue34584, inherits _IssuesUITest, correct [Category(UITestCategories.Shell)]
  • [Issue()] attribute present on HostApp page with PlatformAffected.Android
  • WaitForElement used before interactions
  • ⚠️ #if ANDROID wraps the entire class — This is an accepted pattern in this codebase (e.g., Issue32041AdjustPan.cs, Material3CheckBoxDefaultAppearanceTest.cs) when a fix is strictly Android-only. Acceptable here.
  • ⚠️ NavigateButton AutomationId defined in HostApp but never referenced in the test. Either the test should use it (see Edge Cases above) or it should be removed to avoid confusion.
  • ⚠️ TestShell base class is used correctly; HostApp follows the standard pattern.

5. Flakiness Risk — ✅ Low

  • WaitForElement("TargetLabel") is correctly placed before the assertion
  • App.EnterText + App.PressEnter() is a reliable way to trigger IME-based navigation
  • No Task.Delay / Thread.Sleep
  • No screenshot comparison, so no cursor blink risk
  • Minor concern: App.PressEnter() behavior can vary slightly across Android versions/IME implementations, but this is low risk given the test uses standard Appium APIs

6. Duplicate Coverage — ✅ No duplicates

No existing test covers the specific scenario of Shell navigation with IME active and NavBarIsVisible=False. The similar Shell tests found are for unrelated scenarios.


7. Platform Scope — ✅

The fix is in Android/ShellRenderer.cs (Android-only), the test is guarded with #if ANDROID. Platform scope is correctly matched.


8. Assertion Quality — ⚠️

var label = App.FindElement("TargetLabel");
Assert.That(label, Is.Not.Null);          // ❌ Redundant — WaitForElement already ensures presence

var labelRect = label.GetRect();
Assert.That(labelRect.Y, Is.GreaterThan(0),   // ⚠️ Weak — catches Y=0 but not Y=1 or Y=5
    "TargetLabel should not be at Y=0 (would be under the status bar)");

The first assertion is redundant — WaitForElement + FindElement would already throw if the element doesn't exist. The second assertion is minimal: it catches the specific Y=0 regression but a value of Y=1 or Y=5 would pass, even though content might still be partially under the status bar. Consider either:

  • Using Is.GreaterThanOrEqualTo(statusBarApproximateHeight) with a conservative lower bound
  • Adding VerifyScreenshot() with retryTimeout as a visual check

9. Fix-Test Alignment — ✅

  • Fix: ShellRenderer.SwitchFragment hides soft input before switching fragments
  • Test: Opens Entry (raises IME), navigates via IME action (Enter), verifies destination label is positioned correctly

The test targets exactly what the fix addresses.


Recommendations

  1. Remove Assert.That(label, Is.Not.Null) — it's redundant and adds noise.
  2. Strengthen the Y assertion — use a more meaningful threshold such as Is.GreaterThan(50) (approximate status bar height in dp) or add VerifyScreenshot(retryTimeout: TimeSpan.FromSeconds(2)) for visual regression coverage.
  3. Use or remove NavigateButton — either add a second test method that navigates via button tap with the keyboard open (covering that code path), or remove the NavigateButton from Issue34584_MainPage to avoid dead code.

Note

🔒 Integrity filtering filtered 3 items

Integrity filtering activated and filtered the following items during workflow execution.
This happens when a tool call accesses a resource that does not meet the required integrity or secrecy level of the workflow.

🧪 Test evaluation by Evaluate PR Tests

PureWeen added a commit that referenced this pull request Mar 25, 2026
## Summary

Enables the copilot-evaluate-tests gh-aw workflow to run on fork PRs by
adding `forks: ["*"]` to the `pull_request` trigger and removing the
fork guard from `Checkout-GhAwPr.ps1`.

## Changes

1. **copilot-evaluate-tests.md**: Added `forks: ["*"]` to opt out of
gh-aw auto-injected fork activation guard. Scoped `Checkout-GhAwPr.ps1`
step to `workflow_dispatch` only (redundant for other triggers since
platform handles checkout).

2. **copilot-evaluate-tests.lock.yml**: Recompiled via `gh aw compile` —
fork guard removed from activation `if:` conditions.

3. **Checkout-GhAwPr.ps1**: Removed the `isCrossRepository` fork guard.
Updated header docs and restore comments to accurately describe behavior
for all trigger×fork combinations (including corrected step ordering).

4. **gh-aw-workflows.instructions.md**: Updated all stale references to
the removed fork guard. Documented `forks: ["*"]` opt-in, clarified
residual risk model for fork PRs, and updated troubleshooting table.

## Security Model

Fork PRs are safe because:
- Agent runs in **sandboxed container** with all credentials scrubbed
- Output limited to **1 comment** via `safe-outputs: add-comment: max:
1`
- Agent **prompt comes from base branch** (`runtime-import`) — forks
cannot alter instructions
- Pre-flight check catches missing `SKILL.md` if fork isn't rebased on
`main`
- No workspace code is executed with `GITHUB_TOKEN` (checkout without
execution)

## Testing

- ✅ `workflow_dispatch` tested against fork PR #34621
- ✅ Lock.yml statically verified — fork guard removed from `if:`
conditions
- ⏳ `pull_request` trigger on fork PRs can only be verified post-merge
(GitHub Actions reads lock.yml from default branch)

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@MauiBot
Copy link
Copy Markdown
Collaborator

MauiBot commented Apr 5, 2026

🚦 Gate - Test Before and After Fix

📊 Expand Full Gatebfdf9d1 · Improve Shell test to detect transient status bar overlap

Gate Result: ❌ FAILED

Platform: ANDROID · Base: main · Merge base: 794a9fa6

Test Without Fix (expect FAIL) With Fix (expect PASS)
🖥️ Issue34584 Issue34584 ❌ PASS — 1664s ✅ PASS — 514s
🔴 Without fix — 🖥️ Issue34584: PASS ❌ · 1664s

(truncated to last 15,000 chars)

ackageAsync(AndroidDevice device, PushAndInstallCommand command, CancellationToken token) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at AndroidDeviceExtensions.PushAndInstallPackageAsync(AndroidDevice device, PushAndInstallCommand command, CancellationToken token) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at Xamarin.Android.Tasks.FastDeploy.InstallPackage(Boolean installed) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at Xamarin.Android.Tasks.FastDeploy.InstallPackage(Boolean installed) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at Xamarin.Android.Tasks.FastDeploy.RunInstall() [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]

Build FAILED.

/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: Mono.AndroidTools.InstallFailedException: Unexpected install output: cmd: Failure calling service package: Broken pipe (32) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:  [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at Mono.AndroidTools.Internal.AdbOutputParsing.CheckInstallSuccess(String output, String packageName) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at Mono.AndroidTools.AndroidDevice.<>c__DisplayClass105_0.<InstallPackage>b__0(Task`1 t) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: --- End of stack trace from previous location --- [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: --- End of stack trace from previous location --- [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at AndroidDeviceExtensions.PushAndInstallPackageAsync(AndroidDevice device, PushAndInstallCommand command, CancellationToken token) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at AndroidDeviceExtensions.PushAndInstallPackageAsync(AndroidDevice device, PushAndInstallCommand command, CancellationToken token) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at Xamarin.Android.Tasks.FastDeploy.InstallPackage(Boolean installed) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at Xamarin.Android.Tasks.FastDeploy.InstallPackage(Boolean installed) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at Xamarin.Android.Tasks.FastDeploy.RunInstall() [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
    0 Warning(s)
    1 Error(s)

Time Elapsed 00:16:18.38
* daemon not running; starting now at tcp:5037
* daemon started successfully
  Determining projects to restore...
  All projects are up-to-date for restore.
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13756416
  Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0-android36.0/Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13756416
  Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0-android36.0/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13756416
  Core -> /home/vsts/work/1/s/artifacts/bin/Core/Debug/net10.0-android36.0/Microsoft.Maui.dll
  Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13756416
  Maps -> /home/vsts/work/1/s/artifacts/bin/Maps/Debug/net10.0-android36.0/Microsoft.Maui.Maps.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13756416
  Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.Core/Debug/net10.0-android36.0/Microsoft.Maui.Controls.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13756416
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13756416
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13756416
  Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.Xaml/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Xaml.dll
  Controls.Foldable -> /home/vsts/work/1/s/artifacts/bin/Controls.Foldable/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Foldable.dll
  Microsoft.AspNetCore.Components.WebView.Maui -> /home/vsts/work/1/s/artifacts/bin/Microsoft.AspNetCore.Components.WebView.Maui/Debug/net10.0-android36.0/Microsoft.AspNetCore.Components.WebView.Maui.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13756416
  Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.Maps/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Maps.dll
  Controls.TestCases.HostApp -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Controls.TestCases.HostApp.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13756416
  Graphics -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13756416
  Essentials -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13756416
  Core -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.dll
  Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13756416
  Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Maps.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13756416
  Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13756416
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13756416
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13756416
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13756416
  Microsoft.AspNetCore.Components.WebView.Maui -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.AspNetCore.Components.WebView.Maui.dll
  Controls.Foldable -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Foldable.dll
  Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Maps.dll
  Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Xaml.dll

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:08:10.89
Broadcasting: Intent { act=android.intent.action.CLOSE_SYSTEM_DIALOGS flg=0x400000 }
Broadcast completed: result=0
Broadcasting: Intent { act=android.intent.action.CLOSE_SYSTEM_DIALOGS flg=0x400000 }
Broadcast completed: result=0
  Determining projects to restore...
  Restored /home/vsts/work/1/s/src/Controls/tests/CustomAttributes/Controls.CustomAttributes.csproj (in 1.52 sec).
  Restored /home/vsts/work/1/s/src/TestUtils/src/VisualTestUtils/VisualTestUtils.csproj (in 4 ms).
  Restored /home/vsts/work/1/s/src/TestUtils/src/VisualTestUtils.MagickNet/VisualTestUtils.MagickNet.csproj (in 4.64 sec).
  Restored /home/vsts/work/1/s/src/Controls/tests/TestCases.Android.Tests/Controls.TestCases.Android.Tests.csproj (in 6.29 sec).
  Restored /home/vsts/work/1/s/src/TestUtils/src/UITest.Core/UITest.Core.csproj (in 2 ms).
  Restored /home/vsts/work/1/s/src/TestUtils/src/UITest.Appium/UITest.Appium.csproj (in 2 ms).
  Restored /home/vsts/work/1/s/src/TestUtils/src/UITest.NUnit/UITest.NUnit.csproj (in 444 ms).
  Restored /home/vsts/work/1/s/src/TestUtils/src/UITest.Analyzers/UITest.Analyzers.csproj (in 2.45 sec).
  5 of 13 projects are up-to-date for restore.
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13756416
  Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0/Microsoft.Maui.Graphics.dll
  Controls.CustomAttributes -> /home/vsts/work/1/s/artifacts/bin/Controls.CustomAttributes/Debug/net10.0/Controls.CustomAttributes.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13756416
  Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13756416
  Core -> /home/vsts/work/1/s/artifacts/bin/Core/Debug/net10.0/Microsoft.Maui.dll
  Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13756416
  Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.Core/Debug/net10.0/Microsoft.Maui.Controls.dll
  UITest.Core -> /home/vsts/work/1/s/artifacts/bin/UITest.Core/Debug/net10.0/UITest.Core.dll
  VisualTestUtils -> /home/vsts/work/1/s/artifacts/bin/VisualTestUtils/Debug/netstandard2.0/VisualTestUtils.dll
  UITest.NUnit -> /home/vsts/work/1/s/artifacts/bin/UITest.NUnit/Debug/net10.0/UITest.NUnit.dll
  VisualTestUtils.MagickNet -> /home/vsts/work/1/s/artifacts/bin/VisualTestUtils.MagickNet/Debug/netstandard2.0/VisualTestUtils.MagickNet.dll
  UITest.Appium -> /home/vsts/work/1/s/artifacts/bin/UITest.Appium/Debug/net10.0/UITest.Appium.dll
  UITest.Analyzers -> /home/vsts/work/1/s/artifacts/bin/UITest.Analyzers/Debug/netstandard2.0/UITest.Analyzers.dll
  Controls.TestCases.Android.Tests -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll
Test run for /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll (.NETCoreApp,Version=v10.0)
VSTest version 18.0.1 (x64)

Starting test execution, please wait...
A total of 1 test files matched the specified pattern.
/home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll
[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.8.2+699d445a1a (64-bit .NET 10.0.0)
[xUnit.net 00:00:00.12]   Discovering: Controls.TestCases.Android.Tests
[xUnit.net 00:00:00.31]   Discovered:  Controls.TestCases.Android.Tests
NUnit Adapter 4.5.0.0: Test execution started
Running selected tests in /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll
   NUnit3TestExecutor discovered 1 of 1 NUnit test cases using Current Discovery mode, Non-Explicit run
>>>>> 04/06/2026 20:42:37 FixtureSetup for Issue34584(Android)
>>>>> 04/06/2026 20:42:40 ContentShouldNotRenderUnderStatusBarAfterNavigatingWithKeyboardOpen Start
>>>>> 04/06/2026 20:42:53 ContentShouldNotRenderUnderStatusBarAfterNavigatingWithKeyboardOpen Stop
  Passed ContentShouldNotRenderUnderStatusBarAfterNavigatingWithKeyboardOpen [12 s]
NUnit Adapter 4.5.0.0: Test execution complete

Test Run Successful.
Total tests: 1
     Passed: 1
 Total time: 38.0801 Seconds

🟢 With fix — 🖥️ Issue34584: PASS ✅ · 514s
  Determining projects to restore...
  All projects are up-to-date for restore.
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13756416
  Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0-android36.0/Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13756416
  Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0-android36.0/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13756416
  Core -> /home/vsts/work/1/s/artifacts/bin/Core/Debug/net10.0-android36.0/Microsoft.Maui.dll
  Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13756416
  Maps -> /home/vsts/work/1/s/artifacts/bin/Maps/Debug/net10.0-android36.0/Microsoft.Maui.Maps.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13756416
  Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.Core/Debug/net10.0-android36.0/Microsoft.Maui.Controls.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13756416
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13756416
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13756416
  Controls.Foldable -> /home/vsts/work/1/s/artifacts/bin/Controls.Foldable/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Foldable.dll
  Microsoft.AspNetCore.Components.WebView.Maui -> /home/vsts/work/1/s/artifacts/bin/Microsoft.AspNetCore.Components.WebView.Maui/Debug/net10.0-android36.0/Microsoft.AspNetCore.Components.WebView.Maui.dll
  Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.Xaml/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Xaml.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13756416
  Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.Maps/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Maps.dll
  Controls.TestCases.HostApp -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Controls.TestCases.HostApp.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13756416
  Graphics -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13756416
  Essentials -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13756416
  Core -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.dll
  Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13756416
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13756416
  Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Maps.dll
  Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13756416
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13756416
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13756416
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13756416
  Controls.Foldable -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Foldable.dll
  Microsoft.AspNetCore.Components.WebView.Maui -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.AspNetCore.Components.WebView.Maui.dll
  Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Xaml.dll
  Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Maps.dll

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:06:34.06
Broadcasting: Intent { act=android.intent.action.CLOSE_SYSTEM_DIALOGS flg=0x400000 }
Broadcast completed: result=0
Broadcasting: Intent { act=android.intent.action.CLOSE_SYSTEM_DIALOGS flg=0x400000 }
Broadcast completed: result=0
  Determining projects to restore...
  All projects are up-to-date for restore.
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13756416
  Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0/Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13756416
  Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13756416
  Core -> /home/vsts/work/1/s/artifacts/bin/Core/Debug/net10.0/Microsoft.Maui.dll
  Controls.CustomAttributes -> /home/vsts/work/1/s/artifacts/bin/Controls.CustomAttributes/Debug/net10.0/Controls.CustomAttributes.dll
  Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13756416
  Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.Core/Debug/net10.0/Microsoft.Maui.Controls.dll
  UITest.Core -> /home/vsts/work/1/s/artifacts/bin/UITest.Core/Debug/net10.0/UITest.Core.dll
  VisualTestUtils -> /home/vsts/work/1/s/artifacts/bin/VisualTestUtils/Debug/netstandard2.0/VisualTestUtils.dll
  UITest.NUnit -> /home/vsts/work/1/s/artifacts/bin/UITest.NUnit/Debug/net10.0/UITest.NUnit.dll
  VisualTestUtils.MagickNet -> /home/vsts/work/1/s/artifacts/bin/VisualTestUtils.MagickNet/Debug/netstandard2.0/VisualTestUtils.MagickNet.dll
  UITest.Appium -> /home/vsts/work/1/s/artifacts/bin/UITest.Appium/Debug/net10.0/UITest.Appium.dll
  UITest.Analyzers -> /home/vsts/work/1/s/artifacts/bin/UITest.Analyzers/Debug/netstandard2.0/UITest.Analyzers.dll
  Controls.TestCases.Android.Tests -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll
Test run for /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll (.NETCoreApp,Version=v10.0)
VSTest version 18.0.1 (x64)

Starting test execution, please wait...
A total of 1 test files matched the specified pattern.
/home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll
[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.8.2+699d445a1a (64-bit .NET 10.0.0)
[xUnit.net 00:00:00.10]   Discovering: Controls.TestCases.Android.Tests
[xUnit.net 00:00:00.29]   Discovered:  Controls.TestCases.Android.Tests
NUnit Adapter 4.5.0.0: Test execution started
Running selected tests in /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll
   NUnit3TestExecutor discovered 1 of 1 NUnit test cases using Current Discovery mode, Non-Explicit run
>>>>> 04/06/2026 20:51:17 FixtureSetup for Issue34584(Android)
>>>>> 04/06/2026 20:51:18 ContentShouldNotRenderUnderStatusBarAfterNavigatingWithKeyboardOpen Start
>>>>> 04/06/2026 20:51:27 ContentShouldNotRenderUnderStatusBarAfterNavigatingWithKeyboardOpen Stop
  Passed ContentShouldNotRenderUnderStatusBarAfterNavigatingWithKeyboardOpen [9 s]
NUnit Adapter 4.5.0.0: Test execution complete

Test Run Successful.
Total tests: 1
     Passed: 1
 Total time: 23.7695 Seconds

⚠️ Issues found
  • Issue34584 PASSED without fix (should fail) — tests don't catch the bug
📁 Fix files reverted (2 files)
  • eng/pipelines/ci-copilot.yml
  • src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellRenderer.cs

@MauiBot
Copy link
Copy Markdown
Collaborator

MauiBot commented Apr 5, 2026

🤖 AI Summary

📊 Expand Full Reviewbfdf9d1 · Improve Shell test to detect transient status bar overlap
🔍 Pre-Flight — Context & Validation

Issue: #34584 - Shell page without NavBar jumping when navigating with keyboard open
PR: #34621 - Fix Android layout jump when navigating with IME open and NavBarIsVisible=false
Platforms Affected: Android only
Files Changed: 1 implementation (ShellRenderer.cs), 2 test (Issue34584.cs ×2)

Key Findings

  • Bug: On Android, navigating from a page with the IME open to a page with Shell.NavBarIsVisible="False" causes the destination page to initially render under the status bar (Y≈0) then jump to the correct position — a visible layout glitch.
  • Root cause (PR description): ShellRenderer.SwitchFragment commits the FragmentTransaction while the IME is still visible; Android continues reporting IME-related WindowInsets to the new layout, causing incorrect top insets on first render.
  • PR fix: In SwitchFragment, check rootView.IsSoftInputShowing() on _flyoutView?.AndroidView and call rootView.HideSoftInput() before the fragment transaction. Uses Microsoft.Maui.Platform extension methods.
  • Gate FAILED (this run and prior run): Test passes BOTH without and without the fix. The bug is a transient layout jump that self-corrects; by the time Appium polls rect.Y, the layout has already corrected. Even the updated polling approach (50ms interval over 1s) did not catch the transient.
  • Prior agent review (commit e654b2f): Found 4 independent passing alternatives via try-fix. Attempt 1 (post-transaction RequestApplyInsets + Post()) was selected as best. PR author updated the test in commit bfdf9d1 to use polling, but gate still fails with the new test.
  • Unused element: NavigateButton is defined in HostApp but the UI test never taps it — navigation happens via Entry.Completed (pressing Enter).
  • Side effect concern: HideSoftInput() forcibly dismisses the keyboard before every Shell navigation when the IME is visible, not just for NavBarIsVisible=false pages.
  • _flyoutView?.AndroidView is the DrawerLayout-level root view — any attached view in the window hierarchy works for IME checks, so this is a reasonable choice.
  • Issue reported against 10.0.50, reproduced on Samsung A53 5G (Android 16). Related to Hiding the Shell NavBar on Android Sometimes Overlaps the Android Status Bar #34060 (fixed in 10.0.40 but didn't cover IME case).

Fix Candidates

# Source Approach Test Result Files Changed Notes
PR PR #34621 Call HideSoftInput() before FragmentTransaction in SwitchFragment ❌ Gate FAILED (test doesn't detect transient) ShellRenderer.cs + 2 test files Side effect: forces keyboard away before user intended

🔧 Fix — Analysis & Comparison

Fix Candidates

# Source Approach Test Result Files Changed Notes
PR PR #34621 Call HideSoftInput() before FragmentTransaction in SwitchFragment ❌ Gate FAILED (test doesn't catch transient) ShellRenderer.cs + 2 test files Side effect: forces keyboard away before user intended
1 try-fix (claude-opus-4.6) Post-transaction ExecutePendingTransactionsEx() + ViewCompat.RequestApplyInsets(targetView).Post() in SwitchFragment ✅ PASS ShellRenderer.cs + 2 test files No keyboard dismissal; ~2 lines of production code; test uses OnPreDrawListener to capture first-frame Y
2 try-fix (claude-sonnet-4.6) Fix MauiWindowInsetListener.OnApplyWindowInsets — strip IME insets via Builder.SetInsets(Type.Ime(), Insets.None) during IsImeAnimating ✅ PASS MauiWindowInsetListener.cs + 2 test files Architecturally principled; fixes root cause in shared inset listener; higher blast radius
3 try-fix (gpt-5.3-codex) Defer fragment commit via targetView.Post(...) when IME visible ❌ FAIL ShellRenderer.cs + 2 test files Timing unreliable — IME state not guaranteed stable on next frame
4 try-fix (gpt-5.4) ViewTreeObserver.OnPreDrawListener in ShellContentFragment.OnViewCreated blocks first draw until IME insets clear ✅ PASS ShellContentFragment.cs + 2 test files Scoped to NavBarIsVisible=false pages; doesn't change inset dispatch
5 try-fix (claude-sonnet-4.6) Temporarily set window.setSoftInputMode(SOFT_INPUT_ADJUST_NOTHING) around fragment transaction, restore via DecorView.Post() ✅ PASS ShellRenderer.cs + 2 test files Window-level fix; more lines of code; potential side effects on other navigation scenarios

Cross-Pollination

Model Round New Ideas? Details
claude-opus-4.6 2 Yes FragmentTransaction.SetTransition(0) — remove animation to collapse race window
claude-sonnet-4.6 2 Yes setSoftInputMode(SOFT_INPUT_ADJUST_NOTHING) → ran as Attempt 5
gpt-5.3-codex 2 Yes Status-bar inset shim on destination Shell root — variant of Attempt 2
gpt-5.4 2 Yes Seed ShellContentFragment with stable inset before first layout — variant of Attempt 2
claude-opus-4.6 3 No Confirmed exhausted — all intervention layers covered
claude-sonnet-4.6 3 No Confirmed exhausted
gpt-5.3-codex 3 Yes WindowInsetsAnimationCompat.Callback.OnEnd gate — variant of Attempt 3 (wait for IME to finish)
gpt-5.4 3 Yes Event-driven defer via OnEnd callback — same category as Attempt 3

Exhausted: Yes — Round 3 suggestions are variations of already-tested approaches (IME-wait category, already covered by PR fix and Attempt 3). Strong models (opus, sonnet) confirmed exhaustion.

Selected Fix: Attempt 1 — Post-transaction inset re-dispatch

Reason: Simplest passing fix with the smallest production footprint (~2 lines in SwitchFragment). No keyboard-dismissal side effect unlike the PR's approach. Fixes the specific trigger (missing inset re-dispatch after fragment swap with IME active) without modifying shared infrastructure (MauiWindowInsetListener, which has higher blast radius). Attempt 1's test technique (OnPreDrawListener capturing first-frame Y) is also the cleanest test improvement.


📋 Report — Final Recommendation

⚠️ Final Recommendation: REQUEST CHANGES

Phase Status

Phase Status Notes
Pre-Flight ✅ COMPLETE Issue #34584, Android-only Shell/IME layout jump with NavBarIsVisible=false
Gate ❌ FAILED ANDROID — test passes both with and without fix; transient bug self-corrects before Appium can detect it
Try-Fix ✅ COMPLETE 5 attempts: 4 passing (1, 2, 4, 5), 1 failing (3); Attempt 1 selected as best
Report ✅ COMPLETE

Summary

PR #34621 addresses a real and verified Android bug: navigating to a NavBarIsVisible=false Shell page while the soft keyboard is open causes the destination content to render under the status bar before jumping into the correct position. The production fix in ShellRenderer.cs is functionally sound in concept, but the Gate ❌ FAILED because the test does not actually catch the bug — it passes in both broken and fixed states. The bug is a transient layout glitch that self-corrects before Appium's polling can observe it.

Try-Fix found 4 independently-passing alternatives, including one (Attempt 1) that is simpler and avoids the PR fix's side effect of forcibly dismissing the keyboard before every Shell navigation with an open IME.

Root Cause

During ShellRenderer.SwitchFragment, the fragment transaction is committed while the IME is still visible. The new fragment's very first layout pass receives WindowInsets that include IME insets (because MauiWindowInsetListener.IsImeAnimating is true, causing it to skip inset processing for the new view). This leaves the new fragment with paddingTop = 0, rendering content under the status bar. When the IME state later stabilizes and insets are re-dispatched, the layout corrects — producing the visible jump.

Fix Quality

Issues requiring changes:

  1. Test does not catch the bug (blocking): Assert.That(foundInvalidPosition, Is.False) passes even in the broken state because the layout self-corrects in ~50–200ms, faster than Appium's 50ms polling can reliably sample. The test must capture the first-frame render position, not poll after the fact. The proven approach (used in Attempts 1 and 4) is a ViewTreeObserver.OnPreDrawListener added to the destination page's TargetLabel that records the Y position synchronously on the first draw into a FirstRenderedY label element. The Appium test then reads FirstRenderedY after a short stabilization wait and asserts >= 15dp.

  2. Keyboard-dismissal side effect (minor): HideSoftInput() forcibly closes the keyboard at navigation time in ALL Shell navigations where the IME is open — not just for NavBarIsVisible=false pages. Attempt 1's approach (ExecutePendingTransactionsEx() + ViewCompat.RequestApplyInsets(targetView).Post()) avoids this side effect entirely with ~2 production lines.

  3. NavigateButton unused in test: NavigateButton is declared with AutomationId="NavigateButton" in the HostApp but never used in the UI test. Either use it (to test the button-navigation path) or remove it.

Suggested Fix (Attempt 1)

Replace HideSoftInput() in SwitchFragment with:

// In ShellRenderer.SwitchFragment, before the existing transaction code:
var rootView = _flyoutView?.AndroidView;
bool wasImeShowing = rootView != null && rootView.IsSoftInputShowing();

// ... existing transaction code (unchanged) ...
transaction.CommitAllowingStateLossEx();

if (wasImeShowing)
{
    manager.ExecutePendingTransactionsEx();
    targetView.Post(() =>
    {
        if (targetView.IsAttachedToWindow)
            ViewCompat.RequestApplyInsets(targetView);
    });
}

And update the test to use ViewTreeObserver.OnPreDrawListener in Issue34584_DestinationPage.OnAppearing (or ContentPage constructor) to capture the first-frame Y of TargetLabel into a FirstRenderedY label, then assert FirstRenderedY >= 15 in the Appium test.


@MauiBot MauiBot added s/agent-changes-requested AI agent recommends changes - found a better alternative or issues s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review) labels Apr 5, 2026
Copy link
Copy Markdown
Contributor

@kubaflo kubaflo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test couldn't catch the bug before the fix

Update ContentShouldNotRenderUnderStatusBarAfterNavigatingWithKeyboardOpen to poll for transient layout issues, ensuring TargetLabel is never briefly rendered under the status bar during navigation with the keyboard open. This makes the test more robust against temporary UI glitches.
@jpd21122012
Copy link
Copy Markdown
Contributor Author

Thanks for the feedback!

I’ve updated the test to better capture the issue by sampling the layout position over a short period after navigation, instead of relying on a single snapshot.

This allows detecting transient states where the content is briefly rendered under the status bar when the keyboard is open

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

community ✨ Community Contribution s/agent-changes-requested AI agent recommends changes - found a better alternative or issues s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Shell page without NavBar jumping when navigating with keyboard open

4 participants